﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace System.Xml.WBXml
{
    public class WBXmlDocument : XmlDocument
    {
        private readonly Dictionary<int, Dictionary<byte, string>> LocalAttributes;
        public string VersionNumber;
        private int f_CurrentCodePage;
        private Dictionary<byte, string> f_GlobalTokens;
        private Dictionary<int, Dictionary<byte, string>> f_LocalTokens;
        private bool f_WaitForAttr;
        private bool f_WaitForContent;

        public WBXmlDocument()
        {
            VersionNumber = "1.3";
            InitializeGlobalTokens();
        }

        public WBXmlDocument(Dictionary<int, Dictionary<byte, string>> tokens)
        {
            VersionNumber = "1.3";
            InitializeGlobalTokens();
            f_LocalTokens = tokens;
        }

        private WBXmlDocument(XmlImplementation imp) : base(imp) { VersionNumber = "1.3"; }

        private WBXmlDocument(XmlNameTable nt) : base(nt) { VersionNumber = "1.3"; }

        public WBXmlDocument(Dictionary<int, Dictionary<byte, string>> tokens,
                             Dictionary<int, Dictionary<byte, string>> attributes)
        {
            VersionNumber = "1.3";
            InitializeGlobalTokens();
            f_LocalTokens = tokens;
            LocalAttributes = attributes;
        }

        public Encoding Charset { get; set; }

        private IEnumerable<byte> AddSwitch(int DicId)
        {
            var buffer = DicId == f_CurrentCodePage ? new byte[0] : new byte[] { 0, (byte)DicId };
            f_CurrentCodePage = DicId;
            return buffer;
        }

        private static string FindLastUnclosed(string p)
        {
            var matchs =
                new Regex("<(/)?([a-z]+)[^>]*?(/)?>",
                          RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase).Matches(p);
            var list = new List<string>();
            foreach(Match match in matchs)
            {
                if(match.Groups[1].Value == string.Empty)
                    list.Add(match.Groups[2].Value);
                else
                    list.Remove(match.Groups[2].Value);

                if(match.Groups[3].Value != string.Empty)
                    list.Remove(match.Groups[2].Value);

            }
            return list.Count == 0 ? string.Empty : list[list.Count - 1];
        }

        public byte[] GetBytes()
        {
            var list2 = new List<byte>
                        {
                            (VersionNumber != string.Empty)
                                ? byte.Parse(VersionNumber.Replace("1.", string.Empty))
                                : ((byte) 0),
                            1,
                            0x6a,
                            0
                        };
            var list = list2;
            if(DocumentElement != null)
                using(var reader = new XmlNodeReader(DocumentElement))
                {
                    reader.MoveToFirstAttribute();
                    list.AddRange(readNode(reader));
                }

            return list.ToArray();
        }

        private void InitializeGlobalTokens()
        {
            f_GlobalTokens = new Dictionary<byte, string>();
            foreach(var str in Enum.GetNames(typeof(GlobalToken)))
                f_GlobalTokens.Add((byte)((GlobalToken)Enum.Parse(typeof(GlobalToken), str)), str);

            f_LocalTokens = new Dictionary<int, Dictionary<byte, string>>();
        }

        public void Load(byte[] WBXmlData)
        {
            if(WBXmlData == null) return;
            var builder = new StringBuilder();

            if(WBXmlData.Length > 3)
            {
                VersionNumber = string.Format("1.{0}", WBXmlData[0]);
                try
                {
                    Charset = Encoding.GetEncoding(WBXmlData[2]);
                }
                catch
                {
                    Charset = Encoding.UTF8;
                }
                int num = WBXmlData[3];
                var index = 4 + num;
                while(index < WBXmlData.Length)
                {
                    List<byte> list;
                    if(!f_GlobalTokens.ContainsKey(WBXmlData[index])) goto Label_0198;

                    switch((GlobalToken)WBXmlData[index])
                    {
                        case GlobalToken.SwitchPage:
                            if(++index < WBXmlData.Length)
                            {
                                f_CurrentCodePage = WBXmlData[index];
                                if(!f_LocalTokens.ContainsKey(WBXmlData[index]))
                                    throw new XmlException(string.Format("Code page {0} was not loaded",
                                                                         WBXmlData[index]));
                                break;
                            }
                            continue;
                        case GlobalToken.End:
                            if(!f_WaitForAttr)
                                goto Label_0156;
                            builder.Append(f_WaitForContent ? ">" : "/>");
                            f_WaitForAttr = false;
                            goto Label_0171;
                        case GlobalToken.Entity:
                            continue;
                        case GlobalToken.StrI:
                            index++;
                            if(index >= WBXmlData.Length)
                                continue;
                            list = new List<byte>();
                            goto Label_0105;
                        case GlobalToken.Opaque:
                            {
                                var length = WBXmlData[index++];
                                builder.Append(BitConverter.ToString(WBXmlData, index, length));
                                continue;
                            }
                        default:
                            continue;
                    }
                    index++;
                    continue;
                Label_00F2:
                    list.Add(WBXmlData[index++]);
                    if(index >= WBXmlData.Length) goto Label_010A;
                Label_0105:
                    if(WBXmlData[index] != 0) goto Label_00F2;
                Label_010A:
                    builder.Append(Encoding.UTF8.GetString(list.ToArray()));
                    index++;
                    continue;
                Label_0156:
                    var str = FindLastUnclosed(builder.ToString());
                    builder.AppendFormat("</{0}>", str);
                Label_0171:
                    index++;
                    continue;
                Label_0198:
                    if(!f_LocalTokens[f_CurrentCodePage].ContainsKey((byte)(WBXmlData[index] & 0x3f)))
                    {
                        if(++index >= WBXmlData.Length) break;
                    }
                    else
                    {
                        var str2 = f_LocalTokens[f_CurrentCodePage][(byte)(WBXmlData[index] & 0x3f)];
                        var flag = ((byte)(WBXmlData[index] & 0x80)) == 0x80;
                        var flag2 = ((byte)(WBXmlData[index] & 0x40)) == 0x40;
                        builder.AppendFormat("{2}{0}{1}", str2, flag ? " " : (!flag2 ? "/>" : ">"),
                                             !f_WaitForAttr ? "<" : "");
                        f_WaitForAttr = flag;
                        f_WaitForContent = flag2;
                        index++;
                    }
                }
            }
            base.LoadXml(builder.ToString());
        }

        public override void Load(Stream InStream)
        {
            using(var reader = new StreamReader(InStream))
            {
                Load(Encoding.UTF8.GetBytes(reader.ReadToEnd()));
                reader.Close();
            }
        }

        private byte LookupLocalAttrs(string value, out int DicID)
        {
            DicID = f_CurrentCodePage;
            if(LocalAttributes.ContainsKey(DicID))
                foreach(var key in LocalAttributes[DicID].Keys)
                    if(LocalAttributes[DicID][key] == value) return key;

            foreach(var key in LocalAttributes.Keys)
            {
                foreach(var num3 in LocalAttributes[key].Keys.Where(b => LocalAttributes[key][b] == value))
                {
                    DicID = key;
                    return num3;
                }
            }
            return 0;
        }

        private byte LookupLocalTokens(string value, out int DicID)
        {
            DicID = f_CurrentCodePage;
            if(f_LocalTokens.ContainsKey(DicID))
                foreach(var key in f_LocalTokens[DicID].Keys)
                    if(f_LocalTokens[DicID][key] == value) return key;

            foreach(var key in f_LocalTokens.Keys)
            {
                foreach(var num3 in f_LocalTokens[key].Keys.Where(b => f_LocalTokens[key][b] == value))
                {
                    DicID = key;
                    return num3;
                }
            }
            return 0;
        }

        private IEnumerable<byte> readNode(XmlReader root)
        {
            var list = new List<byte>();
            var depth = root.Depth;
            while(root.Read())
            {
                if(root.EOF) return list.ToArray();
                if(root.IsStartElement())
                {
                    int num2;
                    var lv_LocalTokens = LookupLocalTokens(root.Name, out num2);
                    if(lv_LocalTokens != 0)
                    {
                        list.AddRange(AddSwitch(num2));
                        list.Add(
                            (byte)
                            ((lv_LocalTokens + (!root.IsEmptyElement ? (0x40) : (0))) +
                            (root.HasAttributes ? (0x80) : (0))));

                        if(root.HasAttributes)
                            while(root.MoveToNextAttribute())
                            {
                                lv_LocalTokens = LookupLocalAttrs(root.Name, out num2);
                                if(lv_LocalTokens == 0) continue;
                                list.AddRange(AddSwitch(num2));
                                list.Add(lv_LocalTokens);
                                list.Add(1);
                            }
                    }
                    if((root.NodeType == XmlNodeType.Text) && (root.ValueType == typeof(string)))
                    {
                        list.Add(3);
                        list.AddRange(Encoding.UTF8.GetBytes(root.Value));
                        list.Add(0);
                    }
                    else if(root.Depth > depth)
                        list.AddRange(readNode(root));
                }
                else if((root.NodeType == XmlNodeType.Text) && (root.ValueType == typeof(string)))
                {
                    list.Add(3);
                    list.AddRange(Encoding.UTF8.GetBytes(root.Value));
                    list.Add(0);
                }
                else if(root.NodeType == XmlNodeType.EndElement)
                    list.Add(1);
            }
            return list.ToArray();
        }

        #region Nested type: GlobalToken

        private enum GlobalToken
        {
            End = 1,
            Entity = 2,
            Ext0 = 0xc0,
            Ext1 = 0xc1,
            Ext2 = 0xc2,
            ExtI0 = 0x40,
            ExtI1 = 0x41,
            ExtI2 = 0x42,
            ExtT0 = 0x80,
            ExtT1 = 0x81,
            ExtT2 = 130,
            Literal = 4,
            LiteralA = 0x84,
            LiteralAc = 0xc4,
            LiteralC = 0x44,
            Opaque = 0xc3,
            Pi = 0x43,
            StrI = 3,
            StrT = 0x83,
            SwitchPage = 0
        }

        #endregion

        #region Nested type: PropertiesBag

        private class PropertiesBag : List<KeyValuePair<string, string>>
        {
            private void FillTheBag(XmlReader root)
            {
                var depth = root.Depth;
                var item = new KeyValuePair<string, string>();
                while(root.Read())
                {
                    switch(root.NodeType)
                    {
                        case XmlNodeType.Text:
                            item = new KeyValuePair<string, string>(root.Name, root.Value);
                            break;
                        case XmlNodeType.EndElement:
                            if((item.Key == string.Empty) && (item.Value != string.Empty))
                            {
                                item = new KeyValuePair<string, string>(root.Name, item.Value);
                                base.Add(item);
                                item = new KeyValuePair<string, string>();
                            }
                            if(depth > root.Depth)
                            {
                                return;
                            }
                            goto Label_008E;
                    }
                Label_008E:
                    if(root.IsEmptyElement || root.EOF)
                        return;
                    if(!root.IsStartElement()) continue;
                    var name = root.Name;
                    if(root.HasAttributes)
                    {
                        while(root.MoveToNextAttribute())
                            Add(new KeyValuePair<string, string>(root.Name ?? "", root.Value));
                        root.Read();
                        if((root.NodeType == XmlNodeType.CDATA) && (root.Value.Length == 0))
                            root.Read();
                        if(((root.NodeType == XmlNodeType.Text) && (root.ValueType == typeof(string))) &&
                           (root.Value != string.Empty))
                            Add(new KeyValuePair<string, string>(name, root.Value));
                        if((root.NodeType == XmlNodeType.EndElement) && root.Name.Equals(name))
                        {
                            root.Read();
                            if(root.Depth < depth) return;
                        }
                        else
                            if(root.IsStartElement())
                            {
                                if(root.Depth <= depth) continue;
                                FillTheBag(root);
                            }
                        root.Read();
                    }
                    else
                        Add(new KeyValuePair<string, string>(root.Name, root.Value));
                }
            }
        }

        #endregion
    }
}